﻿using System;
using System.Collections.Generic;
using System.Text;
using Barbar.MapReport.DataSource;

using JsonSharp;
using JsonSharp.Collections;
using JsonSharp.Values;

using Drawing = System.Drawing;

namespace Barbar.MapReport.HtmlRender.Json {
  public class PolylineFactory : JsonFactory {
    private Polyline m_Polyline;
    private Drawing.Color? m_Color;
    private int? m_Weight;

    public PolylineFactory(Polyline polyline, Drawing.Color? color, int? weight) {
      if (polyline == null)
        throw new ArgumentNullException("polyline");
      m_Polyline = polyline;
      m_Color = color;
      m_Weight = weight;
    }

    public override JsonValue ToJsonObject() {
      string encodedPoints;
      string encodedLevels;
      CreateEncodings(m_Polyline, out encodedPoints, out encodedLevels);
      JsonObjects result = new JsonObjects();
      result.Add(new JsonStringValue("points"), new JsonStringValue(encodedPoints));
      result.Add(new JsonStringValue("levels"), new JsonStringValue(encodedLevels));
      if (m_Color.HasValue) {
        result.Add(new JsonStringValue("color"), new JsonColorValue(m_Color.Value));
        result.Add(new JsonStringValue("opacity"), new JsonNumberValue(((double)m_Color.Value.A) / 255.0));
      }
      if (m_Weight.HasValue)
        result.Add(new JsonStringValue("weight"), new JsonNumberValue(m_Weight.Value));
      result.Add(new JsonStringValue("numLevels"), new JsonNumberValue(ZoomLevelCollection.MaxGoogleZoomLevel));
      result.Add(new JsonStringValue("zoomFactor"), new JsonNumberValue(2));

      return result;      
    }

    private static void CreateEncodings(Polyline polyline, out string encodedPoints, out string encodedLevels) {
      int plat = 0;
      int plng = 0;

      StringBuilder sbEncodedPoints = new StringBuilder();
      StringBuilder sbEncodedLevels = new StringBuilder();

      for (int i = 0; i < polyline.Count; ++i) {
        Point point = polyline[i];

        int late5 = (int)Math.Floor(point.Latitude * 1e5m);
        int lnge5 = (int)Math.Floor(point.Longitude * 1e5m);

        int dlat = late5 - plat;
        int dlng = lnge5 - plng;

        plat = late5;
        plng = lnge5;

        sbEncodedPoints.Append(EncodeSignedNumber(dlat) + EncodeSignedNumber(dlng));
        sbEncodedLevels.Append(EncodeNumber(ZoomLevelCollection.MaxGoogleZoomLevel));
      }
      encodedPoints = sbEncodedPoints.ToString();
      encodedLevels = sbEncodedLevels.ToString();
    }

    private static string EncodeSignedNumber(int num) {
      int sgn_num = num << 1;

      if (num < 0)
        sgn_num = ~(sgn_num);

      return EncodeNumber(sgn_num);
    }

    private static string EncodeNumber(int num) {
      StringBuilder encodeString = new StringBuilder();

      while (num >= 0x20) {
        encodeString.Append((char)((0x20 | (num & 0x1f)) + 63));
        num >>= 5;
      }

      encodeString.Append((char)(num + 63));
      return encodeString.ToString();
    }
  }
}
